package a04;

import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.StandardEnvironment;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 探索@Autowired的基本流程
 */
public class DigInject {
    public static void main(String[] args) throws Throwable {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        /**
         * 也是注册bean，不过相比a02中使用registerBeanDefinition的注册，这种写法更简单，故
         * 这里选用这种。不过这种写法相当于自己new了一个bean放容器里，不会有 依赖注入、初始化等
         * 过程
         */
        beanFactory.registerSingleton("bean2", new Bean2());
        beanFactory.registerSingleton("bean3", new Bean3());

        //用来解析@Value
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

        //这个Resolver用来解析${ }的
        beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

        Bean1 bean1 = new Bean1();
        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        //后处理器依赖注入的过程需要从BeanFactory寻找合适的bean
        processor.setBeanFactory(beanFactory);
        //看注释：这里传入的pvs为属性值，将会被应用到bean。但我们这里是从beanFactory，这里传null
//        processor.postProcessProperties(null, bean1, "bean1");
        System.out.println(bean1);

        /**
         * postProcessProperties里面可以看到，大体分为两大步:
         * 1.findAutowiringMetadata找到需要注入的元素的信息
         * 2.metadata.inject(bean, beanName, pvs)开始注入
         * 下面自己调用这两步。findAutowiringMetadata是私有的不能直接调用，使用反射
         */
        Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        method.setAccessible(true);
        InjectionMetadata metadata = (InjectionMetadata) method.invoke(processor, "bean1", Bean1.class, null);
        metadata.inject(bean1, "bean1", null);
        System.out.println(metadata);

        /**
         * metadata.inject()里面做了什么呢？通过findAutowiringMetadata拿到了所有要注入的地方的信息，
         * 在inject里自然就是利用这些信息找到对应的bean，接着就可以反射设置到对象里呢。这里简单介绍下inject
         * 用到的重要的类：DependencyDescriptor。源码流程比较多没看，不过按课程的讲解和我的猜测，从
         * findAutowiringMetadata拿到信息后，会把这些信息封装成一个个DependencyDescriptor，然后
         * BeanFactory会根据这个DependencyDescriptor去寻找到对应的bean
         * 下面看看DependencyDescriptor的使用：
         */

        //对于加AutoWired的属性
        Field bean3 = Bean1.class.getDeclaredField("bean3");
        DependencyDescriptor bean3Descriptor = new DependencyDescriptor(bean3, false);
        Object o = beanFactory.resolveDependency(bean3Descriptor, null, null, null);
        System.out.println("找到bean3: " + o); //这里从BeanFactory找到了Bean3对象

        //对于加AutoWired的方法
        Method setBean2 = Bean1.class.getMethod("setBean2", Bean2.class);
        DependencyDescriptor setBean2Descriptor = new DependencyDescriptor(new MethodParameter(setBean2, 0),true);
        Object o1 = beanFactory.resolveDependency(setBean2Descriptor, null, null, null);
        System.out.println("找到bean2: " + o1);

        //对于加AutoWired和@Value的方法
        Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
        DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
        Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
        System.out.println("找到环境变量：" + o2);



    }
}
